#include "iConfig.hpp"

#include <glf/remote/controller.h>

#include <glf/app.h>
#include <glf/core/event.h>
#include <glf/core/timer.h>
#include <glf/input/inputEvent.h>
#include <glf/remote/canvas.h>

#include <limits>
#undef min
#undef max

using namespace glf;
using namespace remote;

#define NET_RETRY_DELAY_MS		250

#define SHAPE_SIZE				0.03f
#define SHAPE_DURATION			250

#define ICON_SIZE				0.1f
#define ICON_HEIGHT				(ICON_SIZE * 2)
#define ICON_BY_LINE			((int)(2.0f / ICON_SIZE) - 2)

#define SYSTEM_TOP				(-1.0f + (ICON_HEIGHT * 1))
#define KEY_TOP					(-1.0f + (ICON_HEIGHT * 2))

//Controller

Controller::Controller(AddrIp4 addrIP) 
: mCanvas(new Canvas)
, mAddrIP(addrIP)
, mIsServer(addrIP.Addr.ulongAddr == 0)
, mNetRetryTimeMS(std::numeric_limits<int64>::min())
, mIsUpdatedWithoutDraw(false)
{	
	Socket::Init();
	mHostname = Socket::GetHostName();
	GetAppEventMgr().AddEventReceiver(this, (EventManager::Priority)INT_MAX);
}

Controller::~Controller()
{
	GetAppEventMgr().RemoveEventReceiver(this);
	if (IsConnected())
		SendQuit();
	Socket::Deinit();
	delete mCanvas;
}

bool Controller::OnEvent(const CoreEvent &evt)
{
	//serialize events
	if (mIsServer && !evt.IsExternal() && IsConnected())
		SendEvent(evt);
	//draw event
	if (mIsServer || evt.IsExternal())
		DrawEvent(evt);
	return false;
}

void Controller::DrawEvent(const CoreEvent &evt)
{
	//draw shapes
	Canvas::Shape shape;
	shape.mFill = !evt.IsExternal();
	switch (evt.mType) 
	{	
	case AET_SYSTEM:
		{
			const SystemEvent &sysEvt = static_cast<const SystemEvent &>(evt);
			const int pos = (int)sysEvt.mSubType;
			shape.mType = Canvas::ST_RECT;
			shape.mPos = Point<float>(-1.0f + (pos+1) * ICON_SIZE * 2, SYSTEM_TOP);
			shape.mSize = mCanvas->ConvertLength2Size(ICON_SIZE);
			shape.mDurationMS = 4 * SHAPE_DURATION;
			shape.mColor = (Canvas::Color)(((int)Canvas::COLOR_WHITE + pos) % (int)Canvas::COLOR_COUNT);
			mCanvas->PostDrawShape(shape);
			break;
		}
	case AET_ORIENTATION:
		{
			const OrientationEvent &oriEvt = static_cast<const OrientationEvent &>(evt);
			shape.mType = Canvas::ST_TRIANGLE;						
			shape.mSize = Point<float>(0.2f, 0.2f);
			shape.mPos = Point<float>(0.0f, 0.7f);
			switch (oriEvt.mSubType)
			{
			case OET_ASPECTPORTRAIT_REVERSED: 
				shape.mOrientation = Canvas::OT_BOTTOM;
				break;
			case OET_ASPECTLANDSCAPE_RIGHT:
				shape.mOrientation = Canvas::OT_LEFT;
				break;
			case OET_ASPECTLANDSCAPE_LEFT: 
				shape.mOrientation = Canvas::OT_RIGHT;
				break;
			default:		
				shape.mOrientation = Canvas::OT_TOP;
				break;
			}
			shape.mDurationMS = 4 * SHAPE_DURATION;
			shape.mColor = Canvas::COLOR_GRAY;
			mCanvas->PostDrawShape(shape);
			break;
		}
	case IET_MOUSE_BTN_DOWN:
	case IET_MOUSE_BTN_UP:	
	case IET_MOUSE_MOVED:
	case IET_MOUSE_WHEEL:	
		{
			const MouseEvent &msEvt = static_cast<const MouseEvent &>(evt);
			shape.mType = Canvas::ST_CROSS;
			shape.mPos = mCanvas->ConvertPosScreen2Canvas(msEvt.mPos);
			shape.mSize = mCanvas->ConvertLength2Size(SHAPE_SIZE);
			shape.mDurationMS = SHAPE_DURATION;
			switch (evt.mType) 
			{
			case IET_MOUSE_BTN_DOWN:
				if (msEvt.mBtn != Mouse::BTN_WHEELDOWN && msEvt.mBtn != Mouse::BTN_WHEELUP)
					shape.mColor = Canvas::COLOR_RED;
				break;
			case IET_MOUSE_MOVED:
				shape.mColor = Canvas::COLOR_GREEN;
				break;
			case IET_MOUSE_BTN_UP:	
				if (msEvt.mBtn != Mouse::BTN_WHEELDOWN && msEvt.mBtn != Mouse::BTN_WHEELUP)
					shape.mColor = Canvas::COLOR_BLUE;
				break;
			default:					
				break;
			}			
			mCanvas->PostDrawShape(shape);
			break;
		}		
	case IET_KEY_PRESSED:
	case IET_KEY_RELEASED:
		{
			const KeyboardEvent &kbEvt = static_cast<const KeyboardEvent &>(evt);
			int pos = (int)kbEvt.mKey;
			const int line = pos / ICON_BY_LINE;
			pos %= ICON_BY_LINE;
			shape.mType = Canvas::ST_CIRCLE;
			shape.mPos = Point<float>(-1.0f + (pos+1) * ICON_SIZE, KEY_TOP + ICON_HEIGHT * line);
			shape.mSize = mCanvas->ConvertLength2Size(ICON_SIZE);
			shape.mDurationMS = SHAPE_DURATION * 2;
			shape.mColor = evt.mType == IET_KEY_PRESSED ? Canvas::COLOR_RED : Canvas::COLOR_BLUE;
			mCanvas->PostDrawShape(shape);			
			break;
		}
	case IET_TOUCHPAD_BEGAN:
	case IET_TOUCHPAD_MOVED:
	case IET_TOUCHPAD_ENDED:
		{
			const TouchPadEvent &tpEvt = static_cast<const TouchPadEvent &>(evt);
			shape.mType = Canvas::ST_CIRCLE;
			shape.mPos = mCanvas->ConvertPosScreen2Canvas(tpEvt.mPoint);
			shape.mSize = mCanvas->ConvertLength2Size(SHAPE_SIZE);
			shape.mDurationMS = SHAPE_DURATION;
			switch (evt.mType) 
			{
			case IET_TOUCHPAD_BEGAN:
				shape.mColor = Canvas::COLOR_RED;
				break;
			case IET_TOUCHPAD_MOVED:
				shape.mColor = Canvas::COLOR_GREEN;
				break;
			case IET_TOUCHPAD_ENDED:	
				shape.mColor = Canvas::COLOR_BLUE;
				break;
			default:					
				break;
			}			
			mCanvas->PostDrawShape(shape);
			break;
		}
	case IET_GAMEPAD_SENSOR_STICK_CHANGED:
	case IET_GAMEPAD_SENSOR_ACCELEROMETER_CHANGED:
	case IET_GAMEPAD_SENSOR_GYROSCOPE_CHANGED:
		break;
	}
}

void Controller::SendMessage(const uchar *data, int size)
{
	mSocket.Send(data, size);
}

void Controller::SendEvent(const CoreEvent &evt)
{
	EventSerializer *eventSerializer = GetAppEventMgr().GetEventSerializer(evt.mType);
	if (eventSerializer) {
		EventDataWriter writer;		
		MessageHeader header('E');
		//add header data
		writer.Write(header.Data(), header.HeaderSize());
		//add event data
		if (eventSerializer->Serialize(writer, evt)) {			
			header.UpdateMessageSize(writer.Size());
			//rewrite header
			writer.Seek(0);
			writer.Write(header.Data(), header.HeaderSize());
			//send
			SendMessage(writer.GetData(), writer.Size());
		}
	}
}

void Controller::SendIdentification()
{
	EventDataWriter writer;		
	MessageHeader header('I');
	//add header data
	writer.Write(header.Data(), header.HeaderSize());
	//add identidication data
	writer.Write(mHostname.c_str()); //host
	//get platform id
#if GLF_PLATFORM_IPHONE
	static const char platformStr[] = "iPhone";
#elif GLF_PLATFORM_PS3
	static const char platformStr[] = "PS3";
#elif GLF_PLATFORM_WIN32
	static const char platformStr[] = "Windows";
#elif GLF_PLATFORM_PSP2
	static const char platformStr[] = "PSP2";
#elif GLF_PLATFORM_CTR
	static const char platformStr[] = "CTR (3DS)";
#elif GLF_PLATFORM_ANDROID
	static const char platformStr[] = "Android";
#else
	static const char platformStr[] = "Unknown";
#endif
	writer.Write(platformStr); //platform
	//rewrite header
	writer.Seek(0);
	header.UpdateMessageSize(writer.Size());
	writer.Write(header.Data(), header.HeaderSize());
	//send		
	SendMessage(writer.GetData(), writer.Size());						
}

void Controller::SendQuit()
{
	MessageHeader header('Q');
	//send		
	SendMessage(header.Data(), header.HeaderSize());						
}

bool Controller::IsConnected()
{
	return mSocket.IsConnected();
}

bool Controller::Connect()
{	
	if (mSocket.GetLastError() > NETERROR_NONE) { //reset the socket on error
		if (mIsServer) 
			Console::Print("Disconnected from remote receiver\n");
		else
			Console::Print("Disconnected from remote controller\n");
		mSocket.Close();
		mSocket.ClearError();
	}
	const int64 currentTimeMS = GetMilliseconds();
	if (currentTimeMS < mNetRetryTimeMS)
	{
		return false;
	}
	mNetRetryTimeMS = currentTimeMS + NET_RETRY_DELAY_MS;
	if (mIsServer) {
		if (!mSocket.IsOpened()) {
			if (mSocket.OpenTcp(Socket::OPTION_NODELAY)) {
				if (!mSocket.Bind(mAddrIP) || !mSocket.Listen(1))
					mSocket.Close();
			}					
			if (!mSocket.IsOpened()) {				
				Console::Print("Unable to connect to remote receiver\n");
				mCanvas->PostSignal(Canvas::COLOR_RED);
				mNetRetryTimeMS += 8 * NET_RETRY_DELAY_MS;
				return false;
			}
			Console::Print("Remote controller is listening on port %d\n", mAddrIP.port);
			mCanvas->PostSignal(Canvas::COLOR_BLUE);
		}
		AddrIp4 addr;
		if (mSocket.Accept(addr) == -1) {
			mCanvas->PostSignal(Canvas::COLOR_BLUE, true);
			return false;
		}
		SendIdentification();
		Console::Print("Connected to remote receiver\n");
		mCanvas->PostSignal(Canvas::COLOR_GREEN);
		return true;
	}
	else {
		if (!mSocket.IsOpened()) {
			if (!mSocket.OpenTcp(Socket::OPTION_NODELAY)) {				
				Console::Print("Unable to connect to remote controller\n");
				mCanvas->PostSignal(Canvas::COLOR_RED);
				mNetRetryTimeMS += 8 * NET_RETRY_DELAY_MS;
				return false;
			}
			mCanvas->PostSignal(Canvas::COLOR_BLUE);
		}
		if (!mSocket.Connect(mAddrIP)) {
			mCanvas->PostSignal(Canvas::COLOR_BLUE, true);
			return false;
		}
		SendIdentification();
		Console::Print("Connected to remote controller\n");
		mCanvas->PostSignal(Canvas::COLOR_GREEN);
		return true;
	}
}

void Controller::ReceiveEvent(EventDataReader &reader)
{
	ushort type;
	const size_t readPos = reader.Tell();
	reader.Read(type);
	reader.Seek(readPos);
	EventSerializer *eventSerializer = GetAppEventMgr().GetEventSerializer(type);
	if (eventSerializer) {
		CoreEvent::DataType eventData;
		CoreEvent *newEvent = eventSerializer->Unserialize(eventData, reader);
		if (newEvent) {
			newEvent->MarkAsExternal();
			GetAppEventMgr().SendEvent(*newEvent);
		}
	}
}

void Controller::ReceiveIdentification(EventDataReader &reader)
{
	std::string hostname;
	reader.Read(hostname);
	std::string platform;
	reader.Read(platform);
	Console::Print("Receive identification from:\n");
	Console::Print(" - Hostname: %s\n", hostname.c_str());
	Console::Print(" - Platform: %s\n", platform.c_str());	
}

void Controller::ReceiveMessages()
{
	//read data
	//while (mSocket.IsReadable()) {
	while (1) {
		uchar buffer[4096];
		int receiveByteCount = mSocket.Receive(buffer, 4096);
		if (receiveByteCount <= 0)
			break;
		uchar *pBuffer = buffer;
		while (receiveByteCount > 0) {
			//check header size
			if ((size_t)receiveByteCount < MessageHeader::HeaderSize()) {
				Console::Print("Too small message size\n");
				break;
			}
			EventDataReader reader(pBuffer, receiveByteCount);
			MessageHeader header;
			reader.Read(header.Data(), header.HeaderSize());
			//check magic
			if (!header.IsMagic()) {
				Console::Print("Not a remote message\n");
				break;			
			}
			//check size
			if (header.MessageSize() > (size_t)receiveByteCount) {
				Console::Print("Bad message size\n");
				break;			
			}
			//check message type
			switch (header.GetMessageType()) 
			{
			case 'I': //read identification					
				ReceiveIdentification(reader);				
				break;
			case 'E': //read event
				ReceiveEvent(reader);				
				break;
			case 'Q': //quit
				mSocket.Close();
				break;
			default:
				Console::Print("Unknown message type\n");
				break;
			}
			pBuffer += header.MessageSize();
			receiveByteCount -= header.MessageSize();
		}
	}
}

void Controller::Draw(Renderer *renderer)
{		
	//draw
	mCanvas->Update(renderer);
	mIsUpdatedWithoutDraw = false;
}

void Controller::Update()
{	
	//clear shapes
	if (mIsUpdatedWithoutDraw)
		mCanvas->Update(0); 
	//receive
	if (IsConnected())
		ReceiveMessages();
	else
		Connect();	
	mIsUpdatedWithoutDraw = true;
}

